package ordonnanceur;

import generationTache.IGenerationTache;

import java.io.FileNotFoundException;
import java.util.ArrayList;

import outils.ManipulationFichier;
import outils.MathTR;

/**
 * Permet d'appliquer aux taches le couple d'ordonnanceur EDF/TBS
 * @author nicolas rault et matthieu allon
 *
 */
public class EDF_TBS 
{
	private ArrayList<IGenerationTache> listeTacheAperiod;
	private ArrayList<IGenerationTache> listeTachePeriod;
	private ManipulationFichier<IGenerationTache> outils;
	//Defintion de la liste de string a renvoyer a kiwi
	private ArrayList<String> listKiwi;
	private int Up;
	private int ppcm;
	private int duration;

	/**
	 * Permet d'ordonnancer les tâches selon EDF_TBS
	 */
	public EDF_TBS()
	{		
		//Lecture des fichiers contenant les taches periodiques et apériodiques
		lectureFichier();
		listKiwi = new ArrayList<String>();
		if(listeTachePeriod.size() > 0)
		{
			Up = listeTachePeriod.get(0).getUp(); //Up est le même pour toutes les tâches, donc, on prend celui de la premiere tache
		}
		duration = 0;
	}
	
	/**
	 * Permet de calculer le ppcm, ajouter les Di fictifs aux taches apériodique, et de generer la liste pour le parseur kiwi
	 */
	public void ordonnancement()
	{
		//EDF-TBS : donc, utilisation de EDF pour les taches periodiques
		
		//Classement EDF selon la di absolue
		ordonnancementEDF();
		
		//Calcul du PPCM pour les taches périodiques
		calculPPCM();
		
		//Calcul de la di fictive pour chaque tache apériodiques
		calculEtSetDiFictif();	
		//generation de la liste des taches pour kiwi
		generationListKiwi();
				
		//ecriture des performances de l'ordonnanceur
	}
	
	/**
	 * Permet de tester l'ordonnancement du systeme
	 * @return "Le système est ordonnançable" si le test est verifie, "On ne sait pas si le système est ordonnançable" sinon
	 */
	public String testOrdonnancement()
	{
		String condition;
		double U = 0;
		
		for(int i = 0; i<listeTachePeriod.size(); i++)
		{
			IGenerationTache tacheTmp = listeTachePeriod.get(i);
			U += (tacheTmp.getCi()/tacheTmp.getPi());
		}
		if(U<=1)
		{
			condition = "Le système est ordonnançable";
		}
		else
		{
			condition = "On ne sait pas si le système est ordonnançable";
		}		
		
		return condition;
	}
	
	/**
	 * Permet de trier les taches periodiques suivant leur Di
	 */
	private void ordonnancementEDF()
	{
		//Le classement selon EDF doit être réalisé selon la date d'échéance absolue la plus petite
		//(Autrement dit : selon la date di la plus petite à chaque instant ==> réveil et fin d'exécution
		//Donc : ci - di
		
		for( int i = 0; i < listeTachePeriod.size(); i ++)
		{
			int DiI = listeTachePeriod.get(i).getCi() - listeTachePeriod.get(i).getDi();
			for( int j = 0; j < listeTachePeriod.size(); j ++)
			{
				int DiJ = listeTachePeriod.get(j).getCi() - listeTachePeriod.get(j).getDi();
				
				if (DiJ < DiI)
				{
					IGenerationTache listeTacheTmp = listeTachePeriod.get(i);
					listeTachePeriod.get(i).equals(listeTachePeriod.get(j));
					listeTachePeriod.get(j).equals(listeTacheTmp);
				}
			}
		}
	}
	
	/**
	 * Permet de recuperer les taches periodiques et aperiodiques des fichiers existants (TAcheP.txt et TacheAp.txt)
	 */
	private void lectureFichier()
	{
		listeTacheAperiod = new ArrayList<IGenerationTache>();		
		listeTachePeriod = new ArrayList<IGenerationTache>();
		outils = new ManipulationFichier<IGenerationTache>();
		try {
			listeTacheAperiod = outils.read("TacheAp.txt");
			listeTachePeriod = outils.read("TacheP.txt");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Permet de calculer le ppcm par rapport aux Di des taches periodiques
	 */
	private void calculPPCM()
	{		
		ppcm = 0;
		//Calcul du PPCM
		if(listeTachePeriod.size() > 2)
		{
			ppcm = MathTR.calculePPCM(listeTachePeriod.get(0).getPi(), listeTachePeriod.get(1).getPi());
			for(int i = 2; i<listeTachePeriod.size(); i++)
			{
				ppcm = MathTR.calculePPCM(ppcm, listeTachePeriod.get(i).getPi());
			}
		}
		else
		{
			if(listeTachePeriod.size() == 2)
			{
				ppcm = MathTR.calculePPCM(listeTachePeriod.get(0).getPi(),listeTachePeriod.get(1).getPi());
			}
			else
			{
				ppcm = listeTachePeriod.get(0).getPi();
			}
		}		
	}	
	
	/**
	 * Permet de calculer les Di fictifs des taches aperiodiques
	 */
	private void calculEtSetDiFictif()
	{	
		int Us = 100-Up;
		
		/*Attribution d une echeance fictive pour chaque tache
		*/
		for(int i =0;i<listeTacheAperiod.size();i++)
		{
			int diTacheOccurente = 0;
			if (i > 0)
			{
				diTacheOccurente = listeTacheAperiod.get(i-1).getDi();
			}
			listeTacheAperiod.get(i).setDi(Math.max(listeTacheAperiod.get(i).getRi(),diTacheOccurente) 
											+ (listeTacheAperiod.get(i).getCi()/Us)
										   );
		}	
		
		/*Sauvegarde de la liste apres TBS : les informations sont arbitrairement stockées dans 'TacheP.txt', car l'ordonnancement va ensuite être effectué
		 * par EDF*/
		ManipulationFichier<IGenerationTache> manipFichierTBS = new ManipulationFichier<IGenerationTache>();
		manipFichierTBS.sauvegardeTache(listeTachePeriod, "TacheP.txt");		
	}
	
	/**
	 * Permet de trier les taches aperiodiques suivant leur ri
	 */
	private void triTacheAp()
	{
		if(listeTacheAperiod.size() >0)
		{
			boolean fini = false;
			while(!fini){
				fini = true;
				for(int i =0;i<listeTacheAperiod.size();i++)
				{
					if((i+1)<listeTacheAperiod.size()){
						if(listeTacheAperiod.get(i).getRi() > listeTacheAperiod.get(i+1).getRi()){
							IGenerationTache Tmp = listeTacheAperiod.get(i);
							listeTacheAperiod.remove(i);
							listeTacheAperiod.add(i+1,Tmp);
							fini = false;
						}
					}
				}
			}
		}
	}
		
	/**
	 * Permet de generer la liste necessaire au parseur du fichier kiwi
	 */
	private void generationListKiwi()
	{				
		System.out.println("Début du calcul de l'ordonnancement.");
		triTacheAp();
		//Toutes les Taches Periodiques sont pretes, et leur durée au max
		for(int i = 0; i<listeTachePeriod.size(); i++)
		{
			listeTachePeriod.get(i).setActive(true);
			listeTachePeriod.get(i).setDuree(listeTachePeriod.get(i).getCi());
		}
		//Toutes les Taches Apériodiques ont leur durée de connu
		for(int i = 0; i<listeTacheAperiod.size(); i++)
		{
			listeTacheAperiod.get(i).setDuree(listeTacheAperiod.get(i).getCi());
		}
		int tachePrecedente = -1;
		boolean tachePrecedenteActive = false;
		boolean isPeriodique = true;
		int nbPreemption = 0;
		int nbChangementContexte = 0;
		int nbViolationEcheance = 0;
		ArrayList<Integer> listeEcheance = new ArrayList<Integer>();
		//On initialise toutes les dates d'échéances à 0
		for(int i = 0; i<listeTacheAperiod.size();i++)
		{
			listeEcheance.add(0);
		}
		int compteur = 100;
		boolean allApDone = false;
		int i = 0;
		while(i<ppcm && compteur>0)
		{
			int a =0;
			if(i != 0)
			{
				//On vérifie si des taches Periodiques doivent passer en active et on remet a jour leur duree
				for(a = 0; a<listeTachePeriod.size() && i != 0; a++)
				{
					if(i%listeTachePeriod.get(a).getPi() == 0)
					{
						if(listeTachePeriod.get(a).getActive())
						{
							nbViolationEcheance++;
						}
						listKiwi.add("STOP "+a);
						//Son nouveau Ri est son ancien Ri plus Pi
						listeTachePeriod.get(a).setRi(listeTachePeriod.get(a).getRi()+listeTachePeriod.get(a).getPi());
						listeTachePeriod.get(a).setDuree(listeTachePeriod.get(a).getCi());
						listeTachePeriod.get(a).setActive(true);
						//Son nouveau Di est son ancien Di plus Pi
						listeTachePeriod.get(a).setDi(listeTachePeriod.get(a).getDi()+listeTachePeriod.get(a).getPi());
					}
				}
			}
			//On fait la meme chose pour les taches Apériodiques
			for(a = 0; a<listeTacheAperiod.size(); a++)
			{
				if(i == listeTacheAperiod.get(a).getRi())
				{
					listeTacheAperiod.get(a).setActive(true);
				}
			}
			//On vérifie si i correspond aux Di fictif des taches Apériodiques
			for(a = 0; a<listeTacheAperiod.size(); a++)
			{
				if(i == listeTacheAperiod.get(a).getDi()+listeTacheAperiod.get(a).getRi())
				{
					listKiwi.add("STOP "+(a+listeTachePeriod.size()));
					if(listeTacheAperiod.get(a).getActive())
					{
						nbViolationEcheance++;
					}
					listeTacheAperiod.get(a).setActive(false);
				}
			}
			int tache = -1;
			boolean findP = false;
			int petitDi = Integer.MAX_VALUE;
			int petitRi = Integer.MAX_VALUE;
			//On vérifie qu'une tache périodique soit prete
			for(a = 0; a<listeTachePeriod.size(); a++)
			{
				
				if(listeTachePeriod.get(a).getActive())
				{
					if(listeTachePeriod.get(a).getDi() < petitDi)
					{
						tache = a;
						petitDi = listeTachePeriod.get(a).getDi();
						petitRi = listeTachePeriod.get(a).getRi();
						findP = true;
					}
					else if(listeTachePeriod.get(a).getDi() == petitDi)
					{
						if(listeTachePeriod.get(a).getRi()< petitRi)
						{
							tache = a;
							petitDi = listeTachePeriod.get(a).getDi();
							petitRi = listeTachePeriod.get(a).getRi();
							findP = true;
						}
					}
					
				}
			}
			//On vérifie ensuite s'il existe une tacheAp avec un plus petit Di
			for(a = 0;a<listeTacheAperiod.size(); a++)
			{
				if(listeTacheAperiod.get(a).getActive())
				{
					
					if(listeTacheAperiod.get(a).getDi() < petitDi)
					{
						tache = a;
						petitDi = listeTacheAperiod.get(a).getDi();
						petitRi = listeTacheAperiod.get(a).getRi();
						findP = false;
					}
					else if(listeTacheAperiod.get(a).getDi() == petitDi)
					{
						if(listeTacheAperiod.get(a).getRi()< petitRi)
						{
							tache = a;
							petitDi = listeTacheAperiod.get(a).getDi();
							petitRi = listeTacheAperiod.get(a).getRi();
							findP = false;
						}
					}
				}
			}
			if(findP)
			{
				//Une tache Périodique a été trouvé
				
				listeTachePeriod.get(tache).setDuree(listeTachePeriod.get(tache).getDuree()-1);
				if(listeTachePeriod.get(tache).getDuree() == 0)
				{
					listeTachePeriod.get(tache).setActive(false);
				}
				//On vérifie si la tache précédemment traitée était une apériodique
				if(!isPeriodique)
				{
					nbChangementContexte ++;
					isPeriodique = true;						
				}
				//On met a jour la tache précédente avec la tache actuelle.
				if(i!=0)
				{
					if(tachePrecedente != tache)
					{	
						//On vérifie les préemptions
						if(tachePrecedenteActive)
						{
							nbPreemption++;
						}
					}
				}
				tachePrecedenteActive = listeTachePeriod.get(tache).getActive();
				tachePrecedente = tache;
				listKiwi.add(""+tache);
			}
			//Si aucune tache périodique n'est prete, on regarde les taches Apériodiques
			else
			{
				//On a peut-etre trouvé une tache apériodique
				if(tache != -1)
				{
					if(listeTacheAperiod.size() >0)
					{
						//On a trouvé une tache apériodique
						if(listeTacheAperiod.get(tache).getDuree() == listeTacheAperiod.get(tache).getCi())
						{
							//Premiere fois que la tache est utilisée
							//On sauvegarde le i dans le Pi qui est inutilisé pour les Apériodiques
							listeTacheAperiod.get(tache).setPi(i);
						}
						listeTacheAperiod.get(tache).setDuree(listeTacheAperiod.get(tache).getDuree()-1);
						//System.out.println("duree de la tache Ap : "+listeTacheAperiod.get(tache).getDuree());
						if(listeTacheAperiod.get(tache).getDuree() == 0)
						{
							listeTacheAperiod.get(tache).setActive(false);
							//Derniere fois que la tache est utilisée
							//On sauvegarde le i dans la liste d'échéance
							listeEcheance.add(tache,i);
						}
						if(i!=0)
						{
							if(tachePrecedente != listeTachePeriod.size()+tache)
							{	
								//On vérifie les préemptions
								if(tachePrecedenteActive)
								{
									nbPreemption++;
								}
							}
						}
						tachePrecedente = tache+listeTachePeriod.size();
						tachePrecedenteActive = listeTacheAperiod.get(tache).getActive();
						tache = listeTachePeriod.size()+tache;
						//On vérifie si la tache précédente était une tache périodique
						if(isPeriodique)
						{
							nbChangementContexte ++;
							isPeriodique = false;
						}
						//On met a jour la tache précédente avec la tache actuelle.
						listKiwi.add(""+tache);
					}
				}
				else
				{
					listKiwi.add("-1");
				}
			}
			//On vérifie si toutes les taches Ap ont été exécutés
			if(listeTacheAperiod.size()>0 && !allApDone)
			{
				boolean allDone = true;
				for(int b = 0; b<listeTacheAperiod.size(); b++)
				{
					if(listeTacheAperiod.get(b).getDuree() != 0)
					{
						allDone = false;
					}
				}
				if(allDone)
				{
					System.out.println("Toutes les taches Ap ont été exécutées.");
					allApDone = true;
				}
			}
			if(allApDone)
			{
				compteur--;
			}
			i++;
		}	
		int borneMin = Integer.MAX_VALUE;
		int borneMax = 0;
		int moyenne = 0;
		for (int n = 0; n < listeTacheAperiod.size(); n++)
		{
			if (borneMax < listeEcheance.get(n)-listeTacheAperiod.get(n).getPi())
			{
				borneMax = listeEcheance.get(n)-listeTacheAperiod.get(n).getPi();
			}
			if (borneMin > listeEcheance.get(n)-listeTacheAperiod.get(n).getPi())
			{
				borneMin = listeEcheance.get(n)-listeTacheAperiod.get(n).getPi();
			}
			moyenne += listeEcheance.get(n)-listeTacheAperiod.get(n).getPi();
		}
		if(listeTacheAperiod.size()>0)
		{
			moyenne = moyenne/listeTacheAperiod.size();
		}
		outils.sauvegardePerformance("EDF_TBS", borneMin, borneMax, moyenne, nbPreemption, nbChangementContexte, nbViolationEcheance);
		System.out.println("Fin du calcul de l'ordonnancement.");
		duration = i;
	}
		
	/**
	 * Permet de recuperer la duree (en pas) de l'ordonnancement
	 * @return la duration necessaire au fichier kiwi
	 */
	public int getDuration()
	{
		return duration;
	}
	
	/**
	 * Permet de recuperer la liste necessaire au parseur du fichier kiwi
	 * @return la liste necessaire au parseur du fichier kiwi
	 */
	public ArrayList<String> getListeKiwi() {
		return listKiwi;
	}

}
